iT邦幫忙

2024 iThome 鐵人賽

DAY 21
1
JavaScript

TypeScript 初學者也能看的學習指南系列 第 21

TypeScript 初學者也能看的學習指南 21 - Type Alias 型別別名

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20241001/20149362Au9wdAhL9x.png

本篇將來介紹「Type Alias 型別別名」,並會拿 interface 來做對照,讓大家更理解兩者的差異、使用時機
這裡順便附上 interface 的文章,想了解的可以點進去瞧瞧
🔗 Day20 - interface 介面

Type Alias 型別別名

我們在前面的篇章,介紹了許多 TypeScript 的型別,例如 Tuple、Union Type、interface 等
那麼我們可以為這些型別,自定義型別的名稱嗎?
答案是可以的,此時型別別名(Type Aliases)就派上用場啦~

Type Aliases 是 TypeScript 獨有的型別,使用 type 關鍵字就可以來自定義一個型別名稱
它不只可以為任何型別定義一個新名稱,還可以建立更複雜的型別結構,尤其是複雜的物件或聯合型別
等等下面的範例會一一來說明


範例

使用關鍵字 type 來宣告一個型別別名,名稱要「大寫」開頭,例如:Type Person ...
屬性間用「分號」、「逗號」甚至「省略不寫」都是可以的,看團隊撰寫的風格而定。分號或省略不寫在 TypeScript 中更為常見(interface 也是)

  • 物件型別別名
    範例中的 Point 是一個「物件型別別名」,用來描述一個具有 xy 屬性的物件
type Point = {
    x: number;
    y: number;
};

const center: Point = { x: 0, y: 0 }; // ✅ Pass

筆者一開始學的時候常會忘記要在宣告後加上 =,因為 interface 是不需要 = 的,還不熟悉時經常搞混XD

  • 基本型別別名
    我們可以使用「型別別名」為任何型別命名,不僅限於物件,還可以與聯合型別結合
    而 interface 大部分則是用於定義物件或函式簽名
    範例中的 ID 是一個型別別名,代表 number | string 的聯合型別
type ID = number | string;

Type Alias 並不是在創建「新」的型別

為什麼會這樣說呢?
我們先來看看這個擷取自官方文件的範例

完整的 code 如下

declare function getInput(): string;
declare function sanitize(str: string): string;

type UserInputSanitizedString = string;

function sanitizeInput(str: string): UserInputSanitizedString {
  return sanitize(str);
}

// Create a sanitized input
let userInput = sanitizeInput(getInput());

// Can still be re-assigned with a string though
userInput = "new input";

把上面這段拆解

  1. UserInputSanitizedString 被定義為 string 的一個別名
type UserInputSanitizedString = string;
  1. UserInputSanitizedString 實際上和 string 是一模一樣的。當使用這個別名來定義函式回傳或變數時,TypeScript 只會讀到背後的原始型別,也就是 string
function sanitizeInput(str: string): UserInputSanitizedString {
  return sanitize(str);  // sanitize() 接收一個字串型別的參數
}
  1. 即使 userInput 的型別為 UserInputSanitizedString,它仍然可以被賦值為「普通的字串」,對 TypeScript 而言,UserInputSanitizedString 和 string 都是同一個型別,只是名稱上的差異而已

簡單來說,就是換湯不換藥啦!

let userInput = sanitizeInput(getInput()); // sanitizeInput() 會回傳型別別名 UserInputSanitizedString
userInput = "new input";

「型別別名」為定義複雜的型別提供了一種更彈性的方式,同時也讓語義變得更明確,增加可讀性
然而,它並不是在創建一個「新」的型別,這點跟 interface 不一樣,interface 是可以創建具有特定屬性和方法的「新型別」的


常用情境:

情境一:經常重複使用的型別

假設你正在開發一個電商服務,需要處理多種產品的資訊,而每個產品其實都存著在一些共同的屬性,例如:id、商品名稱、價格、庫存量
在不同的功能中,可能會需要重複使用這些產品的屬性結構,這時就可以把這些共同屬性抽出來定義成一個型別別名,像是底下的 Product

type Product = {
    id: number;
    name: string;
    price: number;
    stock: number;
};

// 把型別別名 Product 套用在變數上
let products: Product[] = [];

// 定義一個函式來新增產品到陣列中,傳入的產品參數要符合 Product type 的結構
function addProduct(product: Product) {
    products.push(product);
}

// ❌ Error
addProduct({ id: 1, name: '北拋拋幼咪咪面膜', price: 499 }); 


// ✅ Pass
addProduct({ id: 2, name: '保濕精華乳', price: 999, stock: 100 });

可以看到,Product 型別別名被用於多個函式中來代表產品屬性
在需要修改產品屬性結構時,只需要修改 Product 型別別名就好,不用一一修改使用到這些屬性的地方


情境二:提高程式碼可讀性

假設你正在開發一個有多種狀態的 UI 元件,例如按鈕有各種不同的狀態,例如「成功」、「失敗」、「載入中」。我們可以使用型別別名來定義這些狀態

這裡的 ButtonState 型別別名用來表示按鈕的不同狀態

type ButtonState = 'successful' | 'fail' | 'loading';

接著,我們可以使用這個型別別名來管理 UI 元件的狀態:

function renderButton(state: ButtonState) {     // ButtonState 型別別名
    switch (state) {
        case 'successful':
            console.log('成功狀態的按鈕');
            break;
        case 'fail':
            console.log('失敗狀態的按鈕');
            break;
        case 'loading':
            console.log('載入狀態的按鈕');
    }
}

renderButton('successful'); // ✅ 成功狀態的按鈕
renderButton('fail');       // ✅ 失敗狀態的按鈕
renderButton('loading');    // ✅ 載入狀態的按鈕
renderButton('test');       // ❌ Argument of type '"test"' is not assignable to parameter of type 'ButtonState'.

renderButton 函式根據傳入的 ButtonState 狀態來決定如何渲染按鈕
如果需要增加新的按鈕狀態,只需在 ButtonState 型別別名中增加新的狀態,並在 renderButton 函式中加入對應的處理邏輯就好囉~
若不小心將錯誤的狀態傳遞給按鈕,會在編譯時就補捉到錯誤


Type Alias 特點

  • 可以表示任何型別,包括原始型別、object、array、union types 等
  • 物件必須跟 Type Alias 的定義完全一致,多出或少了屬性都不行
  • 在同個作用域中,型別別名的名稱不可重複(最好盡量都不要重複),這也代表它無法合併屬性
  • 透過 intersection types (&) 擴展其他屬性、方法, interface 則是透過 extends 去擴展
  • 適用於創建複雜的型別結構,如聯合型別或條件型別。例如:type Container<T> = T extends string ? StringContainer : NumberContainer;
  • 並不會創建一個新的型別,而是給現有型別一個新名字(重新命名)

type V.S interface

特性 Type Alias Interface
定義 可以為任何型別賦予別名 用來定義物件的形狀(包含物件屬性、方法) & 函式簽名
是否可擴展 是,透過 intersections 去擴展 是,透過 extends 去擴展
是否可重複定義 是,同名的介面會自動合併

每天的內容有推到 github 上喔


References


上一篇
TypeScript 初學者也能看的學習指南 20 - interface 介面
下一篇
TypeScript 初學者也能看的學習指南 22 - Conditional Types 條件型別
系列文
TypeScript 初學者也能看的學習指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言